/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-11 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

  ==============================================================================
*/

class D3D9FrameBuffer::Pimpl
{
public:
    Pimpl (D3D9Context& context_, const int width_, const int height_,
           const bool wantsDepthBuffer, const bool wantsStencilBuffer)
        : context (context_),
          width (width_),
          height (height_),
          D3DTexture (NULL),
          D3DSurface (NULL),
		  D3DDepthStencilSurface (NULL),
          hasDepthBuffer (false),
          hasStencilBuffer (false)
    {
        jassert (D3D9Helpers::isContextActive());

		D3D9Helpers::pd3dDevice->CreateTexture( width, height,
			1, 
			D3DUSAGE_RENDERTARGET,
			D3DFMT_A8R8G8B8,
			D3DPOOL_DEFAULT,
			&D3DTexture,
			NULL );
		D3DTexture->GetSurfaceLevel(0, &D3DSurface);

  
        if (wantsDepthBuffer || wantsStencilBuffer)
        {
			if( wantsDepthBuffer && wantsStencilBuffer )
			{
				D3D9Helpers::pd3dDevice->CreateDepthStencilSurface( width, height,
					D3DFMT_D24S8,
					D3DMULTISAMPLE_NONE,
					0,
					TRUE,
					&D3DDepthStencilSurface,
					NULL );
			}
			else if( !wantsStencilBuffer )
			{
				D3D9Helpers::pd3dDevice->CreateDepthStencilSurface( width, height,
					D3DFMT_D24X8,
					D3DMULTISAMPLE_NONE,
					0,
					TRUE,
					&D3DDepthStencilSurface,
					NULL );
			}
			else
				jassert ( false );


            hasDepthBuffer = wantsDepthBuffer;
            hasStencilBuffer = wantsStencilBuffer;
        }

    }

    ~Pimpl()
    {
        if (D3D9Helpers::isContextActive())
        {
            if (D3DTexture != NULL)
			{
				D3DTexture->Release();
				D3DTexture = NULL;
			}

            if (D3DDepthStencilSurface != NULL)
			{
				D3DDepthStencilSurface->Release();
				D3DDepthStencilSurface = NULL;
			}

            if (D3DSurface != NULL)
			{
				D3DSurface->Release();
				D3DSurface = NULL;
			}
        }
    }

    bool createdOk() const
    {
        return D3DTexture != NULL && D3DSurface != NULL;
    }

    void bind()
    {
    }

    void unbind()
    {
    }

    D3D9Context& context;
    const int width, height;
	LPDIRECT3DTEXTURE9 D3DTexture;
	LPDIRECT3DSURFACE9 D3DSurface;
	LPDIRECT3DSURFACE9 D3DDepthStencilSurface;
    bool hasDepthBuffer, hasStencilBuffer;

private:
    bool checkStatus() noexcept
    {
    }

    JUCE_DECLARE_NON_COPYABLE (Pimpl);
};

//==============================================================================
class D3D9FrameBuffer::SavedState
{
public:
    SavedState (D3D9FrameBuffer& buffer, const int w, const int h)
        : width (w), height (h),
          data (w * h)
    {
        buffer.readPixels (data, Rectangle<int> (w, h));
    }

    bool restore (D3D9Context& context, D3D9FrameBuffer& buffer)
    {
        if (buffer.initialise (context, width, height))
        {
            buffer.writePixels (data, Rectangle<int> (width, height));
            return true;
        }

        return false;
    }

private:
    const int width, height;
    HeapBlock <PixelARGB> data;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState);
};

//==============================================================================
D3D9FrameBuffer::D3D9FrameBuffer() {}
D3D9FrameBuffer::~D3D9FrameBuffer() {}

bool D3D9FrameBuffer::initialise (D3D9Context& context, int width, int height)
{

    pimpl = nullptr;
    pimpl = new Pimpl (context, width, height, false, false);

    if (! pimpl->createdOk())
        pimpl = nullptr;

    return pimpl != nullptr;
}

bool D3D9FrameBuffer::initialise (D3D9Context& context, const Image& image)
{
    if (! image.isARGB())
        return initialise (context, image.convertedToFormat (Image::ARGB));

    Image::BitmapData bitmap (image, Image::BitmapData::readOnly);

    return initialise (context, bitmap.width, bitmap.height)
            && writePixels ((const PixelARGB*) bitmap.data, image.getBounds());
}

bool D3D9FrameBuffer::initialise (D3D9FrameBuffer& other)
{
    const Pimpl* const p = other.pimpl;

    if (p == nullptr)
    {
        pimpl = nullptr;
        return true;
    }

    const Rectangle<int> area (pimpl->width, pimpl->height);

    if (initialise (p->context, area.getWidth(), area.getHeight()))
    {
		D3D9Helpers::pd3dDevice->StretchRect( 
			other.getFrameBufferSurface(),
			NULL,
			getFrameBufferSurface(),
			NULL,
			D3DTEXF_LINEAR );

        return true;
    }

    return false;
}

void D3D9FrameBuffer::release()
{
    pimpl = nullptr;
    savedState = nullptr;
}

void D3D9FrameBuffer::saveAndRelease()
{
    if (pimpl != nullptr)
    {
        savedState = new SavedState (*this, pimpl->width, pimpl->height);
        pimpl = nullptr;
    }
}

bool D3D9FrameBuffer::reloadSavedCopy (D3D9Context& context)
{
    if (savedState != nullptr)
    {
        ScopedPointer<SavedState> state (savedState);

        if (state->restore (context, *this))
            return true;

        savedState = state;
    }

    return false;
}

int D3D9FrameBuffer::getWidth() const noexcept            { return pimpl != nullptr ? pimpl->width : 0; }
int D3D9FrameBuffer::getHeight() const noexcept           { return pimpl != nullptr ? pimpl->height : 0; }
LPDIRECT3DTEXTURE9& D3D9FrameBuffer::getTexture() const noexcept     { return pimpl->D3DTexture; }

void D3D9FrameBuffer::LostDevice()
{
	if( pimpl == nullptr )
		return;

    if (pimpl->D3DTexture != NULL)
	{
		pimpl->D3DTexture->Release();
		pimpl->D3DTexture = NULL;
	}

    if (pimpl->D3DDepthStencilSurface != NULL)
	{
		pimpl->D3DDepthStencilSurface->Release();
		pimpl->D3DDepthStencilSurface = NULL;
	}

    if (pimpl->D3DSurface != NULL)
	{
		pimpl->D3DSurface->Release();
		pimpl->D3DSurface = NULL;
	}
}

bool D3D9FrameBuffer::makeCurrentRenderingTarget()
{
    jassert (savedState == nullptr);

    if (pimpl == nullptr)
        return false;

	D3D9Helpers::pd3dDevice->SetRenderTarget(0, getFrameBufferSurface());
	if (pimpl->hasDepthBuffer || pimpl->hasStencilBuffer)
		D3D9Helpers::pd3dDevice->SetDepthStencilSurface( pimpl->D3DDepthStencilSurface );

    return true;
}

LPDIRECT3DSURFACE9 D3D9FrameBuffer::getFrameBufferSurface() const
{
    return pimpl->D3DSurface;
}

void D3D9FrameBuffer::getCurrentFrameBufferTarget(LPDIRECT3DSURFACE9& Target)
{
	D3D9Helpers::pd3dDevice->GetRenderTarget(0, &Target);
}

void D3D9FrameBuffer::releaseAsRenderingTarget()
{

	//D3D9Helpers::pd3dDevice->SetRenderTarget(0, D3D9Helpers::D3DBKSurface);
	//D3D9Helpers::pd3dDevice->SetDepthStencilSurface( D3D9Helpers::D3DBKDepthStencilSurface );

}

void D3D9FrameBuffer::clear (const Colour& colour)
{
    if (makeCurrentRenderingTarget())
    {
        D3D9Helpers::clear (colour);
        releaseAsRenderingTarget();
    }
}

void D3D9FrameBuffer::makeCurrentAndClear()
{
    if (makeCurrentRenderingTarget())
    {
		D3D9Helpers::pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL,	0, 1.0f, 0 );
    }
}

bool D3D9FrameBuffer::readPixels (PixelARGB* target, const Rectangle<int>& area)
{
	if (pimpl == nullptr)
		return false;
	else
	{
		D3DLOCKED_RECT LockedRect_D3D;
		RECT LockedRect;
		LockedRect.left = area.getX();
		LockedRect.right = area.getX() + area.getWidth();
		LockedRect.top = area.getY();
		LockedRect.bottom = area.getY() + area.getHeight();

		getFrameBufferSurface()->LockRect( &LockedRect_D3D, &LockedRect, D3DLOCK_READONLY );
		memcpy( target, LockedRect_D3D.pBits, sizeof(PixelARGB) * area.getWidth() * area.getHeight() );
		getFrameBufferSurface()->UnlockRect();
	}

    return true;
}

bool D3D9FrameBuffer::writePixels (const PixelARGB* data, const Rectangle<int>& area)
{

	if (pimpl == nullptr)
		return false;
	else
	{
		D3DLOCKED_RECT LockedRect_D3D;
		RECT LockedRect;
		LockedRect.left = area.getX();
		LockedRect.right = area.getX() + area.getWidth();
		LockedRect.top = area.getY();
		LockedRect.bottom = area.getY() + area.getHeight();

		getFrameBufferSurface()->LockRect( &LockedRect_D3D, &LockedRect, D3DLOCK_NOOVERWRITE );
		memcpy( LockedRect_D3D.pBits, data, sizeof(PixelARGB) * area.getWidth() * area.getHeight() );
		getFrameBufferSurface()->UnlockRect();
	}

    return true;
}

#if JUCE_USE_D3D9_FIXED_FUNCTION
void D3D9FrameBuffer::draw2D (float x1, float y1,
                                float x2, float y2,
                                float x3, float y3,
                                float x4, float y4,
                                const Colour& colour) const
{
    if (pimpl != nullptr)
    {
		D3D9Helpers::pd3dDevice->SetTexture(0, getTexture());
        D3D9Helpers::drawQuad2D (x1, y1, x2, y2, x3, y3, x4, y4, colour);
		D3D9Helpers::pd3dDevice->SetTexture(0, NULL);
    }
}

void D3D9FrameBuffer::draw3D (float x1, float y1, float z1,
                                float x2, float y2, float z2,
                                float x3, float y3, float z3,
                                float x4, float y4, float z4,
                                const Colour& colour) const
{
    if (pimpl != nullptr)
    {
		D3D9Helpers::pd3dDevice->SetTexture(0, getTexture());
        D3D9Helpers::drawQuad3D (x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, colour);
		D3D9Helpers::pd3dDevice->SetTexture(0, NULL);
    }
}


#endif
